
// Copyright (C) 2009-2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using Derm.Render;
using Derm.Scene.Mesh;

namespace Derm
{
	class MeshCodecPluginOBJ : IMeshCodecPlugin
	{
		#region Constructors
		
		/// <summary>
		/// Cobstruct a MeshCodecPluginOBJ
		/// </summary>
		public MeshCodecPluginOBJ()
		{
			
		}
		
		#endregion
		
		#region OBJ File Format
		
		/// <summary>
		/// Geometry vertex. 
		/// </summary>
		private struct ObjGeometryVertex
		{
			/// <summary>
			///  
			/// </summary>
			/// <param name="decl">
			/// A <see cref="T:string[]"/> composing geometry vertex declaration.
			/// </param>
			public ObjGeometryVertex(string[] decl)
			{
				float w = 1.0f;
				
				if (decl[0] == "v")
					throw new ArgumentException("not a vertex declaration");
				if ((decl.Length != 4) && (decl.Length != 5))
					throw new Exception("OBJ vertex has "+(decl.Length-1)+" components.");

				// Parse OBJ vertex coordinates
				float x = Single.Parse(decl[1], NumberStyles.Float, CultureInfo.InvariantCulture);
				float y = Single.Parse(decl[2], NumberStyles.Float, CultureInfo.InvariantCulture);
				float z = Single.Parse(decl[3], NumberStyles.Float, CultureInfo.InvariantCulture);

				if (decl.Length == 5) {
					w = Single.Parse(decl[4], NumberStyles.Float, CultureInfo.InvariantCulture);
					Homogeneous = false;
				}

				// Store vertex coordinates
				Vertex = new Vertex4f(x, y, z, w);

				Homogeneous = true;
			}
			
			/// <summary>
			/// Vertex coordinate.
			/// </summary>
			public readonly Vertex4f Vertex;


			public readonly bool Homogeneous;
		}
		
		/// <summary>
		/// Geometry normal. 
		/// </summary>
		private struct ObjGeometryNormal
		{
			/// <summary>
			///  
			/// </summary>
			/// <param name="decl">
			/// A <see cref="T:string[]"/> composing geometry normal vector declaration.
			/// </param>
			public ObjGeometryNormal(string[] decl)
			{
				float x, y, z;
				
				System.Diagnostics.Debug.Assert(decl[0] == "vn");
				
				// Check token count
				if (decl.Length != 4)
					throw new Exception("OBJ normal has "+(decl.Length-1)+" components.");
				// Parse OBJ vertex coordinates
				x = Single.Parse(decl[1], NumberStyles.Float, CultureInfo.InvariantCulture);
				y = Single.Parse(decl[2], NumberStyles.Float, CultureInfo.InvariantCulture);
				z = Single.Parse(decl[3], NumberStyles.Float, CultureInfo.InvariantCulture);
				// Store vertex coordinates
				mNormal = new Vertex3f(x, y, z);
			}
			
			/// <summary>
			/// Normal vector.
			/// </summary>
			public Vertex3f mNormal;
		}
		
		/// <summary>
		/// Geometry Point. 
		/// </summary>
		private struct ObjElementPoint
		{
			/// <summary>
			///  
			/// </summary>
			/// <param name="decl">
			/// A <see cref="T:System.String[]"/> composing geometry face declaration.
			/// </param>
			/// <param name="iOffset">
			/// A <see cref="System.Int32"/> specifing the indices offset. This is used
			/// for relative indices.
			/// </param>
			public ObjElementPoint(string[] decl, int iOffset)
			{
				System.Diagnostics.Debug.Assert(decl[0] == "p");
				
				// Check token count
				if (decl.Length < 2)
					throw new Exception("OBJ point has "+(decl.Length-1)+" components.");
				
				// Reset data
				mVertex = null;
				// Allocate required data
				mVertex = new int[decl.Length-1];
				
				// Parse OBJ face indices
				for (int i = 0; i < decl.Length-1; i++) {
					int idx = Int32.Parse(decl[i+1], NumberStyles.Integer, CultureInfo.InvariantCulture);
					
					if		(idx < 0) {
						if (iOffset < idx)
							throw new Exception("OBJ point referring invalid relative vertex "+idx+" ("+iOffset+" defined vertices).");
						mVertex[i] = iOffset + idx - 1;
					} else if (idx > 0) {
						mVertex[i] = idx - 1;
					} else
						throw new Exception("OBJ point referring vertex 0.");
				}
			}
			
			/// <summary>
			/// Vectices indices. 
			/// </summary>
			public int[] mVertex;
		}
		
		/// <summary>
		/// Geometry Line.
		/// </summary>
		private struct ObjElementLine
		{
			/// <summary>
			///  
			/// </summary>
			/// <param name="decl">
			/// A <see cref="T:System.String[]"/> composing geometry face declaration.
			/// </param>
			/// <param name="iOffset">
			/// A <see cref="System.Int32"/> specifing the indices offset. This is used
			/// for relative indices.
			/// </param>
			public ObjElementLine(string[] decl, int iOffset)
			{
				System.Diagnostics.Debug.Assert(decl[0] == "l");
				
				// Check token count
				if (decl.Length < 3)
					throw new Exception("OBJ line has "+(decl.Length-1)+" components.");
				
				// Reset data
				mVertex = mTexCoords = null;
				// Allocate required data
				mVertex = new int[decl.Length-1];
				
				// Parse OBJ face indices
				
			}
			
			/// <summary>
			/// Vectices indices. 
			/// </summary>
			public int[] mVertex;
			/// <summary>
			/// Texture coordinate indices. 
			/// </summary>
			public int[] mTexCoords;
		}
		
		/// <summary>
		/// Geometry Face.
		/// </summary>
		private struct ObjElementFace
		{
			/// <summary>
			///  
			/// </summary>
			/// <param name="decl">
			/// A <see cref="T:System.String[]"/> composing geometry face declaration.
			/// </param>
			/// <param name="iOffset">
			/// A <see cref="System.Int32"/> specifing the indices offset. This is used
			/// for relative indices.
			/// </param>
			/// <param name="material">
			/// </param>
			public ObjElementFace(string[] decl, int iOffset, string material)
			{
				string[] refs;
				bool mUseTex = false;
				
				System.Diagnostics.Debug.Assert(decl[0] == "f");
				
				// Check token count
				if (decl.Length < 4)
					throw new Exception("OBJ face has "+(decl.Length-1)+" components.");
				
				// Reset data
				mVertex = mTexCoords = mNormals = null;
				// Store material
				mMaterial = material;
				
				// Determine whether texturing is enabled (first specification rules)
				{
					// Extract indices (v/vt/vn)
					refs = decl[1].Split('/');
					// Check indices count coherence
					if ((refs.Length != 3) && (refs.Length != 1))
						throw new Exception("OBJ face has vertex references composed by "+refs.Length+" indices.");
					// Empty string indicates no texture coordinates
					if (refs.Length == 3)
						mUseTex = (refs[1] != String.Empty);
				}
				
				// Allocate required data
				mVertex = new int[decl.Length-1];
				if (mUseTex == true)
					mTexCoords = new int[decl.Length-1];
				mNormals = new int[decl.Length-1];
				
				// Parse OBJ face indices
				for (int i = 0; i < decl.Length-1; i++) {
					
					// Extract indices (v/vt/vn)
					refs = decl[i+1].Split('/');
					// Check indices count coherence
					if ((refs.Length != 3) && (refs.Length != 1))
						throw new Exception("OBJ face has indices composed by "+refs.Length+" components.");
					
					// Check indices presence coherence
					if (refs[0] == String.Empty)
						throw new Exception("OBJ face doesn't specify vertex reference.");
					if ((refs.Length == 3) && (refs[1] != String.Empty) != mUseTex)
						throw new Exception("OBJ face doesn't specify coherently texture reference.");
					if ((refs.Length == 3) && (refs[2] == String.Empty))
						throw new Exception("OBJ face doesn't specify normal reference.");
					
					// Parse indices
					mVertex[i] = Int32.Parse(refs[0], NumberStyles.Integer, CultureInfo.InvariantCulture) - 1;
					if (refs.Length == 3) {
						if (mUseTex == true)
							mTexCoords[i] = Int32.Parse(refs[1], NumberStyles.Integer, CultureInfo.InvariantCulture) - 1;
						mNormals[i] = Int32.Parse(refs[2], NumberStyles.Integer, CultureInfo.InvariantCulture) - 1;
					}
				}
			}
			
			public static bool HasAssignedMaterial(ObjElementFace f)
			{
				return (f.mMaterial == null);
			}
			
			/// <summary>
			/// Vectices indices. 
			/// </summary>
			public int[] mVertex;
			/// <summary>
			/// Texture coordinate indices. 
			/// </summary>
			public int[] mTexCoords;
			/// <summary>
			/// Normals vector indices. 
			/// </summary>
			public int[] mNormals;
			/// <summary>
			/// Material name. 
			/// </summary>
			public string mMaterial;
		}
		
		/// <summary>
		/// Determine whether a line is a comment. 
		/// </summary>
		/// <param name="line">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.Boolean"/>
		/// </returns>
		private bool IsCommentLine(string line)
		{
			// Ignore initial white spaces
			line = line.TrimStart(' ', '\t');
			
			return (line[0] == '#'); 
		}

		/// <summary>
		/// Regular expression for matching comment lines.
		/// </summary>
		private static readonly Regex sCommentRegex = new Regex("^ *#.*$");
		
		#endregion
		
		#region IMeshCodecPlugin Implementation

		/// <summary>
		/// Determine whether this plugin is available for the current process.
		/// </summary>
		/// <returns>
		/// It returns a boolean value indicating whether the plugin is available for the current
		/// process.
		/// </returns>
		public bool CheckAvailability()
		{
			return (true);
		}

		/// <summary>
		/// Plugin name.
		/// </summary>
		/// <remarks>
		/// This name identity the plugin implementation. It shall be unique in the plugin set available at
		/// runtime.
		/// </remarks>
		public string Name
		{
			get { return ("OBJ"); }
		}
		
		/// <summary>
		/// Check whether a file stream is supported.
		/// </summary>
		/// <param name="path">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.Boolean"/>
		/// </returns>
		/// <remarks>
		/// This routine determine whether a file is supported by examining the file
		/// name extention.
		/// </remarks>
		public bool IsSupported(string path)
		{
			// Lowering file path (case insesitive comparison)
			path = path.ToLower();
			
			// Text OBJ file format
			if (path.EndsWith(".obj") == true)
				return (true);
			// Binary OBJ file format.
			//if (path.EndsWith(".mod") == true)
			//	return (true);
			
			return (false);
		}
		
		public Mesh Load(Stream fs)
		{
			using (StreamReader sr = new StreamReader(fs)) {
				List<Vertex4f> vertexPositions = new List<Vertex4f>();
				bool vertexPositionBufferHomogeneous = true;

				while (sr.EndOfStream == false) {
					Match lineMatch;
					string line = sr.ReadLine();

					if (line == null)
						throw new IOException("unexpected end of file");

					// Skip empty lines
					if (line.Length == 0)
						continue;
					// Skip comment lines
					if (sCommentRegex.IsMatch(line) == true)
						continue;

					if ((lineMatch = sObjVertexData.Match(line)).Success) {
						Group dataArgsGroup = lineMatch.Groups["Args"]; 
						string dataType = lineMatch.Groups["dataType"].Value;
						float[] dataArgs;

						dataArgs = new float[dataArgsGroup.Captures.Count];

						for (int i = 0; i < dataArgs.Length; i++) {
							bool parseResult = Single.TryParse(
								dataArgsGroup.Captures[i].Value,
								NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent,
								CultureInfo.InvariantCulture.NumberFormat,
								out dataArgs[i]
								);

							if (parseResult == false)
								throw new InvalidOperationException("");
						}


						if (dataType == "v") {
							if (dataArgs.Length < 3)
								throw new InvalidOperationException("invalid vertex position line");

							float w = 1.0f;

							// Uses optional 'w' coordinate
							if (dataArgs.Length == 4) {
								w = dataArgs[3];
								if (Math.Abs(w) < Single.Epsilon)
									throw new InvalidOperationException("undefinite vertex coordinate");
								if (Math.Abs(w - 1.0f) >= Single.Epsilon)
									vertexPositionBufferHomogeneous = false;
							}

							// Store vertex coordinates
							vertexPositions.Add(new Vertex4f(dataArgs[0], dataArgs[1], dataArgs[2], w));
						} else if (dataType == "vn") {
							if (dataArgs.Length != 3)
								throw new InvalidOperationException("invalid vertex normal line");
						} else if (dataType == "vt") {
							
						}
					}
				}

				// Define mesh
				Mesh mesh = new Mesh();

				// Define vertex position array
				ArrayBufferObject vertexPositionBuffer;

				if (vertexPositionBufferHomogeneous) {
					ArrayBufferObject<Vertex3f> vertex3PositionBuffer = new ArrayBufferObject<Vertex3f>(BufferObject.Hint.StaticCpuDraw);

					vertex3PositionBuffer.Define((uint)vertexPositions.Count);
					//vertex3PositionBuffer.Define<Vertex4f>(vertexPositions.ToArray());

					vertexPositionBuffer = vertex3PositionBuffer;
				} else {
					ArrayBufferObject<Vertex4f> vertex4PositionBuffer = new ArrayBufferObject<Vertex4f>(BufferObject.Hint.StaticCpuDraw);

					vertex4PositionBuffer.Define(vertexPositions.ToArray());

					vertexPositionBuffer = vertex4PositionBuffer;
				}

				return (mesh);
			}
		}
		
		/// <summary>
		/// Load an mesh from a OBJ file.
		/// </summary>
		/// <param name="fs">
		/// A <see cref="FileStream"/>
		/// </param>
		/// <returns>
		/// A <see cref="Mesh"/>
		/// </returns>
		/// <remarks>
		/// This implementation is based on the specification found at http://local.wasp.uwa.edu.au/~pbourke/dataformats/obj/.
		/// </remarks>
		private Mesh LoadObjFileStream(FileStream fs)
		{
#if false
			Mesh mesh = new Mesh();
			ArrayBufferObject<Vertex3f> vbo = new ArrayBufferObject<Vertex3f>(BufferObject.BufferHint.StaticCpuDraw);
			ArrayBufferObject<Vertex3f> nbo = new ArrayBufferObject<Vertex3f>(BufferObject.BufferHint.StaticCpuDraw);
			StreamReader sr = new StreamReader(fs);
			
			List<ObjGeometryVertex> oVertices = new List<ObjGeometryVertex>(128);
			List<ObjGeometryNormal> oNormals = new List<ObjGeometryNormal>(128);
			
			List<ObjElementPoint> oPoints = new List<ObjElementPoint>(32);
			List<ObjElementLine> oLines = new List<ObjElementLine>(32);
			List<ObjElementFace> oFaces = new List<ObjElementFace>(64);
			
			List<StdMaterialState> oMats = null;
			string oMatCurrent = String.Empty;
			bool oIndexed = false;
			
			#region Collect Mesh Data
			
			// Collect mesh data
			while (sr.EndOfStream == false) {
				string[] tokens;
				string line = sr.ReadLine();
				string decl;
				
				// Skip empty lines
				if (line.Length == 0)
					continue;
				// Skip comment lines
				if (IsCommentLine(line) == true)
					continue;
				
				// Trim white spaces from declaration
				decl = line.Trim();
				// Extract declaration tokens
				tokens = line.Split(' ');
				
				// Vertex data
				if		(tokens[0] == "v") {
					// Geometry vertex
					oVertices.Add(new ObjGeometryVertex(tokens));
				} else if (tokens[0] == "vn") {
					// Vertex normal
					oNormals.Add(new ObjGeometryNormal(tokens));
				} else // Continue to next if
				
				// Elements
				if		(tokens[0] == "p") {
					// Point
					oPoints.Add(new ObjElementPoint(tokens, oVertices.Count));
				} else if (tokens[0] == "l") {
					// Line
					oLines.Add(new ObjElementLine(tokens,  oVertices.Count));
				} else if (tokens[0] == "f") {
					// Face
					oFaces.Add(new ObjElementFace(tokens, oVertices.Count, oMatCurrent));
				} else // Continue to next if
				
				if		(tokens[0] == "s") {
					
				} else // Continue to next if

				if		(tokens[0] == "mtllib") {
					string mMatsLibPath = fs.Name.Substring(0, fs.Name.Length - (fs.Name.Length-fs.Name.LastIndexOf('\\'))) + "\\" +  tokens[1];
					FileStream mMatsFs = new FileStream(mMatsLibPath, FileMode.Open);
					List<StdMaterialState> mats;

					Console.WriteLine("Using material library {0}.", mMatsLibPath);

					// Load materials
					mats = LoadObjMaterials(mMatsFs);
					// Merge with previously loaded materials
					if (oMats == null)
						oMats = new List<StdMaterialState>();
					oMats.AddRange(mats);
					
					mMatsFs.Close();
				} else if (tokens[0] == "usemtl") {
					if (tokens.Length != 2)
						throw new Exception();
					// Store current material
					oMatCurrent = tokens[1];
				} else {
					// Unknown declaration... skip it
					Console.WriteLine("Ununsed OBJ declaration {0} (line contents: {1}).", tokens[0], line);
				}
			}
			
			#endregion

			#region Determine Possible Indexed Mesh Data Layout

			// Note: vertices data could be indexed if each single vertex could be
			// uniquely associated to a single normal, texture coordinate...

			#endregion

			#region Mesh Composition

			Console.WriteLine("Mesh composed by {0} vertices.", oVertices.Count);

			// Determine vertex array
			vbo.AllocateOnClient((uint)oVertices.Count);
			for (int i = 0; i < oVertices.Count; i++)
				vbo[(uint)i] = oVertices[i].mVertex;
			// Allocates normal array
			nbo.AllocateOnClient((uint)oNormals.Count);
			for (int i = 0; i < oNormals.Count; i++)
				nbo[(uint)i] = oNormals[i].mNormal;

			// Compose indexed mesh data
			if (oIndexed == true) {
				// Vertex array is packed for every surface
				//mesh.PackedBuffers.SetVertexBuffer(vbo);
				// Normal array is also packed
				//mesh.PackedBuffers.SetNormalBuffer(nbo);
			}
			
			// Compose mesh with collected indices data
			if (oFaces.Count > 0) {
				List<ObjElementFace> oTri  = new List<ObjElementFace>();
				List<ObjElementFace> oQuad = new List<ObjElementFace>();
				List<ObjElementFace> oPoly = new List<ObjElementFace>();
				
				#region Surface Type Classification
				
				// Separate triangles, quads and polygons
				foreach (ObjElementFace face in oFaces) {
					if	  (face.mVertex.Length == 3)
						oTri.Add(face);
					else if (face.mVertex.Length == 4)
						oQuad.Add(face);
					else
						oPoly.Add(face);
					
					if (oIndexed == true) {
						for (int i = 0; i < face.mVertex.Length; i++) {
							
							if ((face.mVertex[i] < 0) || (face.mVertex[i] >= oVertices.Count))
								throw new Exception("Face vertex indices overflows buffers ("+face.mVertex[i]+").");
							if ((face.mNormals[i] < 0) || (face.mNormals[i] >= oNormals.Count))
								throw new Exception("Face normal indices overflows buffers ("+face.mNormals[i]+").");
							
							// Store normal vector (corresponding to verticex
							nbo[(uint)face.mVertex[i]] = oNormals[face.mNormals[i]].mNormal;
						}
					}
				}
				
				#endregion
				
				#region Triangle Surfaces Definition
				
				// Support surfaces of triangles
				if (oTri.Count > 0) {
					Dictionary<string, List<ObjElementFace>> oTriMats = SeparateFacesByMaterial(oTri);
					
					// Compose surface material
					foreach (KeyValuePair<string, List<ObjElementFace>> l in oTriMats) {
						if (l.Value.Count > 0) {
							Mesh.Surface s;
							
							// Compose surface for specific material
							if (oIndexed == true)
								s = ComposeIndexedSurface(l.Value, 3);
							else
								s = ComposePlainSurface(l.Value, 3, vbo, nbo);

							// Determine surface specific attributes
							//s.ShaderAttributes = new RenderStateSet();
							//	// Compose material state for surface
							//s.ShaderAttributes.Material = new StdMaterialState();
							//if (l.Key != String.Empty) {
							//	try {
							//		StdMaterialState mat = oMats.Find(delegate(StdMaterialState m) { return m.GetName() == l.Key; });
							//		s.ShaderAttributes.Material = mat;
							//	} catch (Exception e) {
							//		//s.ShaderAttributes.Material.SetMaterial(sMatStateDefaultValue);
							//	}
							//} //else
								//s.ShaderAttributes.Material.SetMaterial(sMatStateDefaultValue);
							
							// Include surface into mesh
							mesh.RenderAttributes = s;
						}
					}
				}
				
				#endregion
				
				#region Quadrilateral Surfaces Definition
				
				// Support surfaces of quadrilaterals
				if (oQuad.Count > 0) {
					Dictionary<string, List<ObjElementFace>> oQuadMats = SeparateFacesByMaterial(oQuad);
					
					// Compose surface material
					foreach (KeyValuePair<string, List<ObjElementFace>> l in oQuadMats) {
						if (l.Value.Count > 0) {
							Mesh.Surface s;
							StdMaterialState sMatState = new StdMaterialState();
							
							// Compose material state for surface
							if (l.Key != String.Empty) {
								
							}
							//rss.Material = sMatState;
							// Compose surface for specific material
							s = ComposeIndexedSurface(l.Value, 4);
							
							// Include surface into mesh
							mesh.RenderAttributes = s;
						}
					}
				}
				
				#endregion
			}
			
			#endregion

#endif
			
			return (null);
		}
		
		/// <summary>
		///  
		/// </summary>
		/// <param name="oFaces">
		/// A <see cref="List{ObjElementFace}"/>
		/// </param>
		/// <returns>
		/// A <see cref="T:System.Collections.Generic.Dictionary{String, List{ObjElementFace}}"/>
		/// </returns>
		private Dictionary<string, List<ObjElementFace>> SeparateFacesByMaterial(List<ObjElementFace> oFaces)
		{
			Dictionary<string, List<ObjElementFace>> oFacesMats = new Dictionary<string, List<ObjElementFace>>();
			
			// Collect triangles by material
			foreach (ObjElementFace f in oFaces) {
				List<ObjElementFace> oFacesMat;
				
				if (oFacesMats.TryGetValue(f.mMaterial, out oFacesMat) == false) {
					// Initialize new list for material
					oFacesMat = new List<ObjElementFace>();
					oFacesMats.Add(f.mMaterial, oFacesMat);
				}
				// Collect face for material
				oFacesMat.Add(f);
			}
			
			return (oFacesMats);
		}

#if false
		
		private Mesh.Surface ComposePlainSurface(
			List<ObjElementFace> oFaces,
			uint comp,
			ArrayBufferObject<Vertex3f> mVertices, ArrayBufferObject<Vertex3f> mNormals
			)
		{
			Mesh.Surface s = null;
			ArrayBufferObject<Vertex3f> vbo = new ArrayBufferObject<Vertex3f>();
			ArrayBufferObject<Vertex3f> nbo = new ArrayBufferObject<Vertex3f>();
			uint vIndex;

			Debug.Assert(oFaces != null);
			Debug.Assert(oFaces.Count > 0);
			Debug.Assert(comp > 0);

			// Allocate surface
			switch (comp) {
			case 3:
				s = new Mesh.Surface(Primitive.Triangle);
				break;
			//case 4:
				//s = new Mesh.Surface(Mesh.Primitive.Quad);
			//	break;
			}

			// Allocate memory
			vbo.AllocateOnClient((uint)oFaces.Count*comp);
			nbo.AllocateOnClient((uint)oFaces.Count*comp);
			// Determine vertex data arrays
			vIndex = 0;
			foreach (ObjElementFace f in oFaces) {
				for (uint i = 0; i < comp; i++, vIndex++) {
					vbo[vIndex] = mVertices[(uint)f.mVertex[i]];
					nbo[vIndex] = mNormals[(uint)f.mNormals[i]];
				}
			}
			//s.Attributes.SetVertexBuffer(vbo);
			//s.Attributes.SetNormalBuffer(nbo);

			return (s);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="oFaces">
		/// A <see cref="List{ObjElementFace}"/>
		/// </param>
		/// <param name="comp">
		/// A <see cref="System.Int32"/>
		/// </param>
		/// <returns>
		/// A <see cref="Mesh.Surface"/>
		/// </returns>
		private Mesh.Surface ComposeIndexedSurface(List<ObjElementFace> oFaces, uint comp)
		{
			Mesh.Surface s = null;
			ElementBufferObject<uint> ibo = new ElementBufferObject<uint>();
			uint iboBufferIndex = 0;
			
			Debug.Assert(oFaces != null);
			Debug.Assert(oFaces.Count > 0);
			Debug.Assert(comp > 0);

			switch (comp) {
			case 3:
				s = new Mesh.Surface(Primitive.Triangle);
				break;
			}
			
			// Allocate memory
			ibo.AllocateOnClient((uint)oFaces.Count*comp);
			// Determine indices array
			foreach (ObjElementFace face in oFaces) {
				Debug.Assert(face.mVertex.Length == comp);
				for (uint i = 0; i < comp; i++)
					ibo[iboBufferIndex++] = (uint)face.mVertex[i];
			}
			// Define a new surface
			//s.Attributes.SetVertexBuffer(0);
			//s.Attributes.SetNormalBuffer(0);
			//s.Attributes.SetElementBuffer(ibo);
			
			return (s);
		}

		/// <summary>
		/// Load a set of materials from a MTL file. 
		/// </summary>
		/// <param name="fs">
		/// A <see cref="FileStream"/>
		/// </param>
		/// <returns>
		/// A <see cref="List<StdMaterialState>"/>
		/// </returns>
		private List<StdMaterialState> LoadObjMaterials(FileStream fs)
		{
			List<StdMaterialState> mats = null;
			StreamReader sr = new StreamReader(fs);
			StdMaterialState state = new StdMaterialState();
			
			while (sr.EndOfStream == false) {
				string[] tokens;
				string line = sr.ReadLine();
				string decl;
				
				// Trim white spaces from declaration
				decl = line.Trim();
				// Skip empty lines
				if (line.Length == 0)
					continue;
				// Skip comment lines
				if (IsCommentLine(line) == true)
					continue;
				
				// Extract declaration tokens
				tokens = line.Split(' ');
				
				// Vertex data
				if		(tokens[0] == "newmtl") {
					if (mats != null) {
						// Store the material in the list
						mats.Add(state);
						// Reset material
						state = new StdMaterialState();
					}
					// Create material
					state = new StdMaterialState();
					state.Name = tokens[1];
					// Start appending materials
					if (mats == null)
						mats = new List<StdMaterialState>();
				} else if (tokens[0] == "Ka") {
					float r, g, b;
					
					if (tokens.Length != 4)
						throw new Exception("OBJ material ambient color has "+(tokens.Length-1)+" components.");
					
					// Parse RGB color components
					r = Single.Parse(tokens[1], NumberStyles.Float, CultureInfo.InvariantCulture);
					if ((r < 0.0f) || (r > 1.0f))
						throw new Exception("OBJ material ambient color R component is "+r+". Value values are in the range [0,1]");
					g = Single.Parse(tokens[2], NumberStyles.Float, CultureInfo.InvariantCulture);
					if ((g < 0.0f) || (g > 1.0f))
						throw new Exception("OBJ material ambient color G component is "+g+". Value values are in the range [0,1]");
					b = Single.Parse(tokens[3], NumberStyles.Float, CultureInfo.InvariantCulture);
					if ((b < 0.0f) || (b > 1.0f))
						throw new Exception("OBJ material ambient color B component is "+b+". Value values are in the range [0,1]");
					
					// Store RGB ambient color
					state.SetAmbient(new ColorRGBAF(r, g, b));
				} else if (tokens[0] == "Kd") {
					float r, g, b;
					
					if (tokens.Length != 4)
						throw new Exception("OBJ material diffuse color has "+(tokens.Length-1)+" components.");
					
					// Parse RGB color components
					r = Single.Parse(tokens[1], NumberStyles.Float, CultureInfo.InvariantCulture);
					if ((r < 0.0f) || (r > 1.0f))
						throw new Exception("OBJ material diffuse color R component is "+r+". Value values are in the range [0,1]");
					g = Single.Parse(tokens[2], NumberStyles.Float, CultureInfo.InvariantCulture);
					if ((g < 0.0f) || (g > 1.0f))
						throw new Exception("OBJ material diffuse color G component is "+g+". Value values are in the range [0,1]");
					b = Single.Parse(tokens[3], NumberStyles.Float, CultureInfo.InvariantCulture);
					if ((b < 0.0f) || (b > 1.0f))
						throw new Exception("OBJ material diffuse color B component is "+b+". Value values are in the range [0,1]");
					
					// Store RGB diffuse color
					state.SetDiffuse(new ColorRGBAF(r, g, b));
				} else if (tokens[0] == "Ks") {
					float r, g, b;
					
					if (tokens.Length != 4)
						throw new Exception("OBJ material specular color has "+(tokens.Length-1)+" components.");
					
					// Parse RGB color components
					r = Single.Parse(tokens[1], NumberStyles.Float, CultureInfo.InvariantCulture);
					if ((r < 0.0f) || (r > 1.0f))
						throw new Exception("OBJ material specular color R component is "+r+". Value values are in the range [0,1]");
					g = Single.Parse(tokens[2], NumberStyles.Float, CultureInfo.InvariantCulture);
					if ((g < 0.0f) || (g > 1.0f))
						throw new Exception("OBJ material specular color G component is "+g+". Value values are in the range [0,1]");
					b = Single.Parse(tokens[3], NumberStyles.Float, CultureInfo.InvariantCulture);
					if ((b < 0.0f) || (b > 1.0f))
						throw new Exception("OBJ material specular color B component is "+b+". Value values are in the range [0,1]");
					
					// Store RGB specular color
					state.SetSpecular(new ColorRGBAF(r, g, b));
				} else if (tokens[0] == "Ns") {
					float s;
					
					if (tokens.Length != 2)
						throw new Exception("OBJ material shiness color has "+(tokens.Length-1)+" components.");
					
					// Parse shiness value
					s = Single.Parse(tokens[1], NumberStyles.Float, CultureInfo.InvariantCulture);
					// Spec. say: Ns values normally range from 0 to 1000. Map the range into 0, 128?
					s = s * 128.0f/1000.0f;
					// Check shiness value
					if ((s < 0.0f) || (s > 128.0f))
						throw new Exception("OBJ material shiness is "+s+". Value values are in the range [0,128]");
					
					// Store material shiness
					state.SetShiness(s);
				} else if ((tokens[0] == "Tr") || (tokens[0] == "d")) {
					float a;
					
					if (tokens.Length != 2)
						throw new Exception("OBJ material shiness color has "+(tokens.Length-1)+" components.");
					
					// Parse transparency value
					a = Single.Parse(tokens[1], NumberStyles.Float, CultureInfo.InvariantCulture);
					// Check transparency value
					if ((a < 0.0f) || (a > 1.0f))
						throw new Exception("OBJ material trasparency is "+a+". Value values are in the range [0,1]");
				} else if (tokens[0] == "illum") {
					
				} else if (tokens[0] == "mapKa") {
					
				} else if (tokens[1] == "Ni") {
					// Store material refraction index
				} else {
					
				}
			}
			
			// Store the last material in the list
			if (mats != null) {
				mats.Add(state);
			}
			
			return (mats);
		}

#endif

		/// <summary>
		/// Regular expression for matching OBJ vertex data.
		/// </summary>
		private static readonly Regex sObjVertexData = new Regex(@"^(?<DataType>vn|vt|v) (?<Args>[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)+");

		/// <summary>
		/// Regular expression for matching OBJ element data.
		/// </summary>
		private static readonly Regex sObjElementData = new Regex(@"^(?<ElementType>p|l|f) (?<Args>[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)+");
		
		#endregion
	}
}
